#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import re
import time

from common.const import SubJobStatusEnum, DBLogLevel, CMDResult, ReportDBLabel
from common.env_common import get_install_head_path
from common.exception.common_exception import ErrCodeException
from common.util.cmd_utils import cmd_format, get_livemount_path
from common.util.common_utils import change_dir_owner_recursively
from common.util.exec_utils import exec_cp_cmd, su_exec_rm_cmd, exec_mv_cmd
from informix import LOGGER
from informix.comm.comm import check_instance_close, set_xbsa_path, \
    exec_cmd_with_informix_env, remove_file_dir, set_permisson, mkdir_set_permissions, get_informix_dir, \
    backup_mount_bind, aggregate_single_copy_object_data, set_xbsa_user, set_xbsa_josn, \
    output_execution_result_ex, execute_cmd, update_db_new_instance_name_and_id, check_path_in_white_list, \
    get_uid_gid, check_xbsa_config, set_informix_conf, set_user, get_informix_version, modify_path_permissions, \
    exec_cmd_with_informix_env_according_to_system, identify_env_user, change_dir_permission
from informix.comm.common_cmd import CommonCommand
from informix.comm.common_models import LogDetail, SubJobDetails, CopyDataModel
from informix.comm.const import InformixBaseCommand, InformixCustomParmConstant, ErrorCode, InformixPath, \
    PERMISSION_644, PERMISSION_755, PERMISSION_640, PERMISSION_600, \
    SubJobName, JsonConstant, InformixInfo, GbaseCommand, GbaseInfo
from informix.service.BaseService import MetaServiceWorker


class RestoreBase(MetaServiceWorker):
    def __init__(self, job_manager, param_obj):
        super().__init__(job_manager, param_obj)
        self._pid = ''
        self.param_obj = param_obj
        self._job_id = job_manager.job_id
        self._sub_job_id = job_manager.sub_job_id
        self._sub_job_name = ''
        self._json_param_object = None
        self._logdetail = None
        self.resource_id = ''
        self.old_instance_name = ''
        self.arch_copy_id = ''
        self.old_server_name = ''
        self.copy_server_num = ''
        self.arch_copy_server_num = ''
        self._progress = 0
        self.log_detail_errcode = None
        self.log_detail_param = None
        self.last_copy_id = ''
        self.full_copy_id = ''
        self._log_area_list = []
        self.target_location = ''
        self.base_backupset = []
        self.log_copy_id = ''
        self.instance_type = ''
        self.targetenv_nodes = []
        self._data_area = ''
        self._cache_area = ''
        self.servernum = ""
        self._onconfig_path_curr = ''
        self._onconfig_name_curr = ''
        self.resoruce_type = ''
        self._sqlhosts_path_curr = ''
        self._instance_name_curr = ''
        self.restore_type = ''
        self.copy_instance_name = ''
        self._version = ''
        self.copy_rootdbs_path = ''
        self.rootdbs_path_curr = ''
        self.copy_version = ''
        self._meta_area = ''
        self._log_area = ''
        self.arch_copy_instance_name = ''
        self._env_dict = {}
        self.local_logbackup_path = ''
        self._err_info = ''
        self.onconfig_file_curr = ''
        self.instance_name_copy = ''
        self.ixbar_sub_path = None
        self.db_user = identify_env_user() if identify_env_user() else InformixInfo.INFORMIX_USER

    def get_log_common(self):
        return f"jobId: {self._job_id} subjobId: {self._sub_job_id}."

    def init_param(self):
        self.servernum = self.param_obj.get_servernum()
        self.old_server_name = self.param_obj.old_server_name()
        self.copy_server_num = self.param_obj.get_copy_server_num()
        self._sqlhosts_path_curr = self.param_obj.get_sqlhosts_path_curr(self.db_user)
        self._onconfig_name_curr = self.param_obj.get_onconfig_name_curr(self.db_user)
        self.onconfig_file_curr = self.param_obj.get_onconfig_path_curr(self.db_user)
        self.arch_copy_id = self.param_obj.get_source_backup_copy_id()
        self.resource_id = self.param_obj.get_resource_id()
        self._sub_job_name = self.param_obj.get_subjob_jobname()
        self.local_logbackup_path = os.path.join(self.param_obj.get_local_log_backup_path(), self.resource_id) \
            if self.param_obj.get_local_log_backup_path() else ""
        self.copy_instance_name = self.param_obj.get_copy_instance_name()
        self.arch_copy_instance_name = self.param_obj.get_arch_copy_instance_name()
        self.instance_name_copy = self.arch_copy_instance_name if self.arch_copy_instance_name else \
            self.copy_instance_name
        self.arch_copy_server_num = self.param_obj.get_arch_copy_server_num()
        self.copy_rootdbs_path = self.param_obj.get_copy_root_dbs()
        self.rootdbs_path_curr = self.param_obj.get_rootdbs_path_curr()
        self.resoruce_type = self.param_obj.get_resoruce_type()
        self._instance_name_curr = self.param_obj.get_instance_name_curr()
        self._env_dict = {
            InformixCustomParmConstant.INSTANCE_NAME: self._instance_name_curr,
            InformixCustomParmConstant.SQLHOSTS_PATH: self._sqlhosts_path_curr,
            InformixCustomParmConstant.CONFIG_NAME: self._onconfig_name_curr
        }
        self._onconfig_path_curr = self.param_obj.get_onconfig_path_curr(self.db_user)
        self.last_copy_id = self.param_obj.get_source_backup_copy_id()
        self.full_copy_id = self.param_obj.get_full_copy_id()
        self.log_copy_id = self.param_obj.get_log_copyid()
        self.old_instance_name = self.param_obj.get_old_instance_name()
        self.target_location = self.param_obj.get_target_location()
        self.base_backupset = self.param_obj.get_base_backupset()
        self.instance_type = self.param_obj.get_instance_type()
        self.targetenv_nodes = self.param_obj.get_targetenv_nodes()
        self._data_area = get_livemount_path(self._job_id, self.param_obj.get_data_path())
        self._version = self.param_obj.get_version()
        self.copy_version = self.param_obj.get_copy_version()
        self._cache_area = self.param_obj.get_cache_path()
        self._meta_area = self.param_obj.get_meta_path()
        if "livemount" in self._meta_area:
            data_dir = re.search("Database_([a-z0-9]+)_InnerDirectory_su", self._meta_area)
            self._meta_area = os.path.join(self._data_area, f"source_policy_{data_dir.group(1)}_Context_Global_MD")
        self._log_area = self.param_obj.get_log_area()
        if "livemount" in self._log_area:
            get_livemount_path(self._job_id, self._log_area.split("livemount")[0])
        self._log_area_list = self.param_obj.get_log_area_list()
        self.restore_type = self.param_obj.get_restore_type()
        self.ixbar_sub_path = 'etc' if self.db_user == InformixInfo.INFORMIX_USER else 'backups'

    def allow(self):
        # 版本校验
        if self._sub_job_name in [SubJobName.START, SubJobName.SET_PRIMARY, SubJobName.SET_SECONDARY]:
            return
        if self._version != self.copy_version:
            LOGGER.error("Check version fail. %s", self.get_log_common())
            raise ErrCodeException(ErrorCode.ERROR_DIFFERENT_VERSION.value)
        # 校验配置文件是否存在
        if not self.check_controls_file():
            LOGGER.error("Controls not exists. %s", self.get_log_common())
            raise ErrCodeException(ErrorCode.ERROR_ONCONFIG_FILE_NOT_EXIST.value)
        # 校验实例状态
        if not check_instance_close(self._env_dict, self.db_user):
            LOGGER.error("Instance not close. %s", self.get_log_common())
            raise ErrCodeException(ErrorCode.ERR_DB_IS_RUNNING.value)
        if self.copy_rootdbs_path != self.rootdbs_path_curr:
            LOGGER.error("Inconsistent rootdbs file paths. %s", self.get_log_common())
            raise ErrCodeException(ErrorCode.ERR_ROOT_DB_FILE_INCONSISTENCY.value)
        # 检查是否配置了xbsa库
        if not check_xbsa_config(self.onconfig_file_curr):
            LOGGER.error(f"The xbsa library is not configured. {self.get_log_common()}")
            raise ErrCodeException(ErrorCode.ERR_DB_NOT_CONFIG_XBSA.value)
        return

    def restore_pre(self):
        """
        恢复前置任务
        """
        # 进度上报信息
        self._support_progress(SubJobStatusEnum.RUNNING, 1, log_level=DBLogLevel.INFO)
        # 创建cache仓tmp目录
        cache_tmp_path = os.path.join(self._cache_area, "tmp", self.last_copy_id)
        ret = CommonCommand.create_dir(cache_tmp_path, self.db_user)
        if not ret:
            LOGGER.error("Failed to create %s.", self.get_log_common())
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        # 创建cache仓tmp目录
        if not set_xbsa_user(self.db_user):
            LOGGER.error("Failed to set xbsa user.")
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        if not self.update_log_db_file():
            LOGGER.error("Update log db file fail. %s", self.get_log_common())
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        if not self.update_data_db_file():
            LOGGER.error("Update data db file fail. %s", self.get_log_common())
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        if not self.update_db_file_and_copy_file_by_cur_instance():
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        if not self.pre_db_file():
            LOGGER.error(f"Failed to pre db file.")
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        if not self.pre_data_xbsa_cache_info():
            LOGGER.error(f"Failed to pre chche info.")
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        if not self.pre_task_info():
            LOGGER.error(f"Failed to pre task info.")
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        if not self.pre_log_xbsa_cache_info():
            LOGGER.error(f"Failed to pre log info.")
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        # 准备ixbar.servernum文件
        if not self.pre_ixbar_file() or not self.pre_sm_version_file():
            LOGGER.error("Prepare ixbar file fail. %s", self.get_log_common())
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.ERROR)
            return
        LOGGER.info("Exec restore pre succ")
        if not set_xbsa_josn() or not set_informix_conf(self.db_user):
            LOGGER.error("Set xbsa xml fail.%s", self.get_log_common())
            self._support_progress(SubJobStatusEnum.FAILED, 1, log_level=DBLogLevel.INFO)
            return
        self._support_progress(SubJobStatusEnum.COMPLETED, 100, log_level=DBLogLevel.INFO)
        return

    def pre_sm_version_file(self):
        """
        配置xbsa版本文件（informix11）
        """
        if self.db_user != InformixInfo.INFORMIX_USER:
            LOGGER.info("This is not a Informix env, no need to modify the sm_version file")
            return True
        LOGGER.info("Start to configure sm version file.")
        informix_version = get_informix_version()
        if informix_version == "11":
            informix_dir = get_informix_dir()
            dst_sm_version_file = os.path.join(informix_dir, "etc", "sm_versions")
            if not os.path.exists(dst_sm_version_file):
                src_sm_version_file = os.path.join(informix_dir, "etc", "sm_versions.std")
                ret = exec_cp_cmd(src_sm_version_file, dst_sm_version_file, self.db_user, None,
                                  is_check_path_owner=False)
                if not ret:
                    LOGGER.error(f"Failed to generate the sm_versions file, {self.get_log_common()}.")
                    return False
                LOGGER.info("The sm_versions file is generated successfully.")
            else:
                LOGGER.info("The sm_versions file already exists.")
        else:
            LOGGER.info("The current version is not informix 11, and the sm_versions file is not required.")
        return True

    def pre_data_xbsa_cache_info(self):
        """
        准备 data cache信息，供xbsa server读取
        """
        cache_info = {
            "cacheRepoPath": self._cache_area,
            "metaRepoPath": self._meta_area,
            "copyId": self.last_copy_id,
            "taskId": self._job_id,
            "hostKey": self._instance_name_curr
        }

        LOGGER.info(f"Pre data cache Info: {cache_info}")
        cache_file = f"informix_{self.instance_name_copy}_{self._instance_name_curr}_restore_data.txt"
        cache_file_path = os.path.join(InformixPath.AGENT_STEMP_PATH, cache_file)
        ret, real_path = check_path_in_white_list(cache_file_path)
        if not ret:
            raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
        output_execution_result_ex(cache_file_path, cache_info)
        return set_permisson(cache_file_path, PERMISSION_644)

    def pre_log_xbsa_cache_info(self):
        """
        准备log cache信息，供xbsa server读取
        """
        instance_name_curr = self._instance_name_curr
        cache_info = {
            "cacheRepoPath": self._cache_area,
            "metaRepoPath": self._meta_area,
            "copyId": self.last_copy_id,
            "taskId": self._job_id,
            "hostKey": instance_name_curr
        }
        LOGGER.info(f"Pre xbsa log cache Info: {cache_info}")
        cache_file = f"informix_{self.instance_name_copy}_{self._instance_name_curr}_restore_log.txt"
        cache_file_path = os.path.join(InformixPath.AGENT_STEMP_PATH, cache_file)
        ret, real_path = check_path_in_white_list(cache_file_path)
        if not ret:
            raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
        output_execution_result_ex(cache_file_path, cache_info)
        set_user(cache_file_path, InformixInfo.RDADMIN)
        if not set_permisson(cache_file_path, PERMISSION_600):
            LOGGER.error(f"Failed to set the permission on the log cache parameter file: {cache_file_path}")
            return False
        if not self.pre_log_backup_xbsa_info():
            LOGGER.error("Failed to prepare xbsa info.")
            return False
        return True

    def pre_log_backup_xbsa_info(self):
        """
        准备log 备份cache信息，供xbsa server读取（时间点恢复时）
        """
        LOGGER.info("Start to configure the log parameter file.")
        if not self.restore_type:
            LOGGER.info("The log cache file does not need to be configured during data restoration.")
            return True
        if not self.pre_log_backup_path():
            return False
        if not self.pre_log_backup_xbsa_cache_info():
            return False
        return self.pre_log_backup_xbsa_task_info()

    def pre_log_backup_path(self):
        LOGGER.info("Start to configure the log backup path.")
        # 1. 创建日志备份根目录
        logbackup_rootpath = os.path.join(self._cache_area, f"{self._job_id}_{self.resource_id}")
        if not mkdir_set_permissions(logbackup_rootpath, self.db_user):
            LOGGER.error("Failed to create the log backup root directory.")
            return False
        if not set_permisson(logbackup_rootpath, PERMISSION_755):
            LOGGER.error("Failed to set the permission on the log backup directory.")
            return False

        # 2. 创建日志备份目录
        logbackup_path = os.path.join(logbackup_rootpath, self.resource_id)
        if not mkdir_set_permissions(logbackup_path, self.db_user):
            LOGGER.error("Failed to create the log backup directory.")
            return False

        # 3. 创建日志备份meta目录
        meta_path = os.path.join(logbackup_rootpath, "meta", "1")
        if not mkdir_set_permissions(meta_path, InformixInfo.RDADMIN):
            LOGGER.error("Failed to create the log backup meta directory.")
            return False

        # 4. 创建元数据目录
        objectmeta_path = os.path.join(meta_path, InformixInfo.OBJECTMEAT, self._instance_name_curr)
        if not mkdir_set_permissions(objectmeta_path, InformixInfo.RDADMIN):
            LOGGER.error("Failed to create the log backup objectmeta directory.")
            return False

        # 5. 创建日志备份cache目录
        cache_path = os.path.join(logbackup_rootpath, "tmp", "1")
        if not mkdir_set_permissions(cache_path, InformixInfo.RDADMIN):
            LOGGER.error("Failed to create the log backup cache directory.")
            return False
        return True

    def pre_log_backup_xbsa_cache_info(self):
        LOGGER.info("Start to configure the log cache file.")
        # 1. 创建cache文件供xbsa服务端读取
        logbackup_rootpath = os.path.join(self._cache_area, f"{self._job_id}_{self.resource_id}")
        task_id = f"{self._job_id}_{self.resource_id}"
        cache_info = {
            "cacheRepoPath": logbackup_rootpath,
            "metaRepoPath": logbackup_rootpath,
            "copyId": '1',
            "taskId": task_id,
            "hostKey": self._instance_name_curr
        }
        LOGGER.info(f"Log backup cache information: {cache_info}")
        xbsa_cache_file_name = f"informix_{self.instance_name_copy}_{self._instance_name_curr}_backup_log.txt"
        xbsa_cache_file_path = os.path.join(InformixPath.AGENT_STEMP_PATH, xbsa_cache_file_name)
        ret, real_path = check_path_in_white_list(xbsa_cache_file_path)
        if not ret:
            raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
        if os.path.exists(xbsa_cache_file_path):
            xbsa_cache_bak = f"informix_{self.instance_name_copy}_{self._instance_name_curr}_backup_log_bak.txt"
            xbsa_cache_bak_path = os.path.join(InformixPath.AGENT_STEMP_PATH, xbsa_cache_bak)
            ret, real_path = check_path_in_white_list(xbsa_cache_bak_path)
            if not ret:
                raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
            LOGGER.info(f"Rename the log backup cache file, src: {xbsa_cache_file_path}, "
                        f"dst: {xbsa_cache_bak_path}.")
            exec_mv_cmd(xbsa_cache_file_path, xbsa_cache_bak_path)
        output_execution_result_ex(xbsa_cache_file_path, cache_info)
        set_user(xbsa_cache_file_path, InformixInfo.RDADMIN)
        return set_permisson(xbsa_cache_file_path, PERMISSION_600)

    def pre_log_backup_xbsa_task_info(self):
        LOGGER.info("Start to configure the log task file.")
        logbackup_rootpath = os.path.join(self._cache_area, f"{self._job_id}_{self.resource_id}")
        logbackup_path = os.path.join(logbackup_rootpath, self.resource_id)
        task_info = {
            "repositories": [{
                "role": 0,  # 当前仓库的角色，0-master，1-slave
                "deviceSN": "xxxx",  # 设备ESN
                "filesystems": [{  # 数据仓的文件系统列表，不包含cache仓和元数据仓，因为cache仓和元数据仓通过dws_cacheInfo.txt指定了
                    "id": "xxx",
                    "name": "xxx",
                    "sharePath": "xxx",
                    "mountPath": [logbackup_path]  # 框架挂载的文件系统本地挂载点路径，不区分协议类型（NFS/dataturbo）
                }]
            }],
            "archiveFileServers": [{
                "ip": "xxx",
                "port": 30066,
                "sslEnabled": ""
            }],
            "taskType": 0,  # 任务类型，0-备份，1-恢复，2-删除
            "copyType": 1  # 副本类型，与agent thrift接口定义的enum CopyDataType类型一致
        }
        LOGGER.info(f"Log backup task information: {task_info}")
        logbackup_rootpath = os.path.join(self._cache_area, f"{self._job_id}_{self.resource_id}")
        task_file_path = os.path.join(logbackup_rootpath, "tmp", "1", f"taskInfo_{self._instance_name_curr}.txt")
        ret, real_path = check_path_in_white_list(task_file_path)
        if not ret:
            raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
        output_execution_result_ex(task_file_path, task_info)
        set_user(task_file_path, InformixInfo.RDADMIN)
        return set_permisson(task_file_path, PERMISSION_600)

    def pre_db_path(self):
        copy_instance_name = self.arch_copy_instance_name if self.arch_copy_instance_name else self.copy_instance_name
        db_path = os.path.join(f"{get_install_head_path()}/DataBackup", "meta", "1/objectmeta", f"{copy_instance_name}")
        # 创建db目录
        if not check_path_in_white_list(db_path):
            raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
        if not mkdir_set_permissions(db_path, InformixInfo.RDADMIN):
            return False

        # 设置db目录权限
        try:
            uid, gid = get_uid_gid(InformixInfo.RDADMIN)
        except Exception as e_info:
            LOGGER.exception(e_info)
            return False
        db_meta_path = os.path.join(f"{get_install_head_path()}/DataBackup", "meta")
        change_dir_owner_recursively(db_meta_path, uid, gid)
        return True

    def pre_db_file(self):
        LOGGER.info(f"arch_copy_id: {self.arch_copy_id}, last_copy_id: {self.last_copy_id}")
        _instance_name_curr = self.arch_copy_instance_name if self.arch_copy_instance_name else self.copy_instance_name
        copy_id = self.arch_copy_id if self.arch_copy_id else self.last_copy_id
        src_path = os.path.join(self._meta_area, 'meta', copy_id, 'objectmeta', _instance_name_curr,
                                f'{_instance_name_curr}.db')
        dst_path = os.path.join(self._cache_area, 'meta', self.last_copy_id, 'objectmeta', f'backupkey.db')
        log_db_path = os.path.join(f"{get_install_head_path()}/DataBackup", "meta", "1/objectmeta",
                                   f"{_instance_name_curr}", f'{_instance_name_curr}.db')
        if not self.pre_db_path():
            LOGGER.error("Failed to create the directory.")
            return False
        ret = exec_cp_cmd(src_path, log_db_path, None, None, is_check_path_owner=False)
        if not ret:
            LOGGER.error("Failed to copy %s", self.get_log_common())
            return False
        cache_meta_path = os.path.join(self._cache_area, "meta", self.last_copy_id, 'objectmeta')
        ret = mkdir_set_permissions(cache_meta_path, 'rdadmin')
        if not ret:
            LOGGER.error("Failed to create %s.%s", cache_meta_path, self.get_log_common())
            return False
        ret = exec_cp_cmd(src_path, dst_path, None, None, is_check_path_owner=False)
        if not ret:
            LOGGER.error("Failed to copy %s", self.get_log_common())
            return False
        cmd = f'chmod -R 755 {os.path.join(self._cache_area, "meta")}'
        ret, out, err = execute_cmd(cmd)
        if ret != CMDResult.SUCCESS:
            LOGGER.error("Failed to set 755 %s.%s", err, self.get_log_common())
            return False
        return True

    def pre_task_info(self):
        """
        生成备份路径信息供xbsa读取
        """
        task_info = {
            "repositories": [{
                "role": 0,  # 当前仓库的角色，0-master，1-slave
                "deviceSN": "xxxx",  # 设备ESN
                "filesystems": [{  # 数据仓的文件系统列表，不包含cache仓和元数据仓，因为cache仓和元数据仓通过dws_cacheInfo.txt指定了
                    "id": "xxx",
                    "name": "xxx",
                    "sharePath": "xxx",
                    "mountPath": []  # 框架挂载的文件系统本地挂载点路径，不区分协议类型（NFS/dataturbo）
                }]
            }],
            "archiveFileServers": [{
                "ip": "xxx",
                "port": 30066,
                "sslEnabled": ""
            }],
            "taskType": 1,  # 任务类型，0-备份，1-恢复，2-删除
            "copyType": 1  # 副本类型，与agent thrift接口定义的enum CopyDataType类型一致
        }
        task_file_path = os.path.join(self._cache_area, "tmp", self.last_copy_id,
                                      f'taskInfo_{self._instance_name_curr}.txt')
        ret, real_path = check_path_in_white_list(task_file_path)
        if not ret:
            raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
        repositories = task_info.get("repositories", [])
        filesystems = repositories[0].get("filesystems", [])
        copy_id = self.arch_copy_id if self.arch_copy_id else self.last_copy_id
        filesystems[0]["mountPath"] = [os.path.join(self._data_area, copy_id)]
        output_execution_result_ex(task_file_path, task_info)
        ret = set_permisson(os.path.join(self._cache_area, "tmp"), PERMISSION_755)
        if not ret:
            return False
        ret = set_permisson(os.path.join(self._cache_area, "tmp", self.last_copy_id), PERMISSION_755)
        if not ret:
            return False
        ret = set_permisson(task_file_path, PERMISSION_644)
        if not ret:
            return False
        return True

    def gen_sub_job(self):
        """
        只针对集群实例拆分子任务
        """
        pass

    def restore_base(self):
        """
        执行恢复子任务
        """
        log_detail = LogDetail(log_info=ReportDBLabel.RESTORE_SUB_START_COPY, log_info_param=[self._sub_job_id],
                               log_detail='', log_level=DBLogLevel.INFO,
                               log_detail_param=[])
        self._support_progress(SubJobStatusEnum.RUNNING, 1, log_detail=log_detail, log_level=DBLogLevel.INFO)
        if not self.clean_instance_process():
            LOGGER.error("Failed to clear the instance process.")
            self._support_progress(SubJobStatusEnum.FAILED)
            return
        # 设置onbar调用的xbsa动态库地址
        if not set_xbsa_path(self._env_dict, self.db_user):
            LOGGER.error("Set xbsa lib fail.")
            self._support_progress(SubJobStatusEnum.FAILED)
            return

        # 下发恢复命令,并检查返回结果
        if self.restore_type and not self.check_log_restore_cmd_result():
            self._support_progress(SubJobStatusEnum.FAILED)
            return

        if not self.restore_type:
            ret, out, err = self.check_data_restore_cmd_result()
            if not ret:
                LOGGER.error("Exec restore fail.")
                log_detail = LogDetail(log_info="plugin_restore_subjob_fail_label", log_info_param=[self._sub_job_id],
                                       log_detail=ErrorCode.EXEC_BACKUP_RECOVER_CMD_FAIL, log_level=DBLogLevel.ERROR,
                                       log_detail_param=["Restore", err])
                self._support_progress(SubJobStatusEnum.FAILED, 100, log_detail=log_detail)
            self._support_progress(SubJobStatusEnum.COMPLETED, 100)
            return

        if self.resoruce_type == JsonConstant.INFORMIX_SINGLE_INSTANCE and self.local_logbackup_path:
            if not self.check_arch_current_log_cmd_result():
                LOGGER.error("Exec arch cmd fail.")
            self.clean_arch_recorde_and_log_copy(self.db_user)
        self.remove_ixbar()
        self._support_progress(SubJobStatusEnum.COMPLETED, 100)

    def remove_ixbar(self):
        LOGGER.info("Start delete expired ixbar files.")
        informix_dir = get_informix_dir()
        if not informix_dir:
            LOGGER.error("Get informix dir fail", self.get_log_common())
            return False
        dst_ixbar_path = os.path.join(informix_dir, self.ixbar_sub_path, f'ixbar.{self.servernum}')
        if not remove_file_dir(dst_ixbar_path):
            return False
        return True

    def restore_post(self):
        """
        执行恢复后置任务
        """
        self.clean_ixbar_file()
        log_xbsa_cache_bak = f"informix_{self.instance_name_copy}_{self._instance_name_curr}_backup_log_bak.txt"
        log_xbsa_cache_bak_path = os.path.join(InformixPath.AGENT_STEMP_PATH, log_xbsa_cache_bak)
        if os.path.exists(log_xbsa_cache_bak_path):
            log_xbsa_cache = f"informix_{self.instance_name_copy}_{self._instance_name_curr}_backup_log.txt"
            log_xbsa_cache_path = os.path.join(InformixPath.AGENT_STEMP_PATH, log_xbsa_cache)
            LOGGER.info(f"Rename the log backup cache file, src: {log_xbsa_cache_bak_path}, "
                        f"dst: {log_xbsa_cache_path}.")
            exec_mv_cmd(log_xbsa_cache_bak_path, log_xbsa_cache_path)
        _instance_name_curr = self.arch_copy_instance_name if self.arch_copy_instance_name else self.copy_instance_name
        self._support_progress(SubJobStatusEnum.COMPLETED, 1, 0, DBLogLevel.INFO)
        return

    def clean_ixbar_file(self):
        informix_dir = get_informix_dir(self.db_user)
        ixbar_path = "etc" if self.db_user == InformixInfo.INFORMIX_USER else "backups"
        ixbar_file = os.path.join(informix_dir, ixbar_path, "ixbar.0")
        clean_ixbar_file_cmd = f'su - {self.db_user} -c "rm -f {ixbar_file}"'
        LOGGER.info(f"Start to delete ixbar.0 file, cmd: {clean_ixbar_file_cmd}.")
        ret, out, err = execute_cmd(clean_ixbar_file_cmd)
        if ret != CMDResult.SUCCESS:
            LOGGER.error(f"Delete ixbar.0 file failed, error: {err}")
            return False
        return True

    def set_info(self):
        pass

    def start_instance(self):
        """
        恢复完成后，将数据库状态由quite切换为online,切换数据库状态成功与否不影响任务状态
        """
        LOGGER.info("Start to start the instance.")
        ret, out, err = exec_cmd_with_informix_env(self._env_dict, InformixBaseCommand.SWITCH_TO_ONLINE, self.db_user)
        if ret != CMDResult.SUCCESS:
            self.paras_result_set_errinfo(err)
            LOGGER.warning("Start instance fail", self.get_log_common())

        if not self._restart_instance():
            LOGGER.warning("Failed to restart the GBase instance", self.get_log_common())
            return False

        if not self.check_arch_current_log_cmd_result():
            LOGGER.warning("Failed to trigger log archiving.", self.get_log_common())
        self.clean_arch_recorde_and_log_copy(self.db_user)
        self.remove_ixbar()
        return True

    def check_controls_file(self):
        LOGGER.info("Start to check whether the database metadata file exists.")
        if not os.path.exists(self._onconfig_path_curr):
            LOGGER.error("Onconfig file does not exist", self.get_log_common())
            return False
        if not os.path.exists(self._sqlhosts_path_curr):
            LOGGER.error("Sqlhosts file does not exist", self.get_log_common())
            return False
        return True

    def check_data_restore_cmd_result(self):
        """
        执行恢复命令,检查返回结果
        """
        file_path = os.path.join(self._cache_area, f"{self._sub_job_id}_restore_info.txt")
        LOGGER.info(f"Start exec data cmd: {cmd_format(InformixBaseCommand.RESTORY_PHYSIC, file_path)}")
        try:
            modify_path_permissions(file_path)
        except Exception as exception:
            LOGGER.error(f"exception: {exception}")
        ret, out, err = exec_cmd_with_informix_env_according_to_system(self._env_dict,
                                                                       cmd_format(InformixBaseCommand.RESTORY_PHYSIC,
                                                                                  file_path),
                                                                       self.db_user)
        if ret != CMDResult.SUCCESS:
            LOGGER.error(f"Exec onbar -r -p fail err:%s,out:%s.%s", err, out, self.get_log_common())
            self.paras_result_set_errinfo(err)
            return False, out, err
        # 集群备节点恢复不执行onbar -r -l命令
        if self.instance_type == 'Read-Only (Sec)':
            return True, out, err
        file_path = os.path.join(self._cache_area, f"{self._sub_job_id}_restore_info.txt")
        LOGGER.info(f"Start exec log cmd: {cmd_format(InformixBaseCommand.RESTORY_LOGIC, file_path)}")
        ret, out, err = exec_cmd_with_informix_env_according_to_system(self._env_dict,
                                                                       cmd_format(InformixBaseCommand.RESTORY_LOGIC,
                                                                                  file_path),
                                                                       self.db_user)
        if ret != CMDResult.SUCCESS:
            LOGGER.error(f"Exec onbar -r -l fail err:%s,out:%s.%s", err, out, self.get_log_common())
            self.paras_result_set_errinfo(err)
            return False, out, err
        return True, out, err

    def check_log_restore_cmd_result(self):
        time_array = time.localtime(int(self.restore_type))
        other_style_time = time.strftime("%Y-%m-%d %H:%M:%S", time_array)
        file_path = os.path.join(self._cache_area, f"{self._sub_job_id}_restore_info.txt")
        cmd = cmd_format(InformixBaseCommand.RESTORY_TIMESTEP, other_style_time, file_path)
        LOGGER.info(f"Start exec restore cmd: {cmd}")
        ret, out, err = exec_cmd_with_informix_env(self._env_dict, cmd, self.db_user)
        LOGGER.info(f'ret={ret}=================out={out}==========err={err}')
        if ret != CMDResult.SUCCESS:
            LOGGER.error(f"Exec onbar -r -t fail err:%s,out:%s.%s", err, out, self.get_log_common())
            if ret not in CMDResult.INFORMIX_CERTIFICATE_ABOUT_TO_EXPIRE or err.find(
                    "Your evaluation license will expire") == -1:
                self.paras_result_set_errinfo(err)
                return False
        return True

    def check_arch_current_log_cmd_result(self):
        ret, out, err = exec_cmd_with_informix_env(self._env_dict,
                                                   InformixBaseCommand.FROCE_NEXT_LOGICAL_LOG, self.db_user)
        if ret != CMDResult.SUCCESS:
            LOGGER.error(f"Exec onbar -b -l fail. err:{err}, out: {out}. {self.get_log_common()}")
            self.paras_result_set_errinfo(err)
            return False
        LOGGER.info("Backup the current log succeeded.")
        self.remove_ixbar()
        return True

    def clean_arch_recorde_and_log_copy(self, db_user=InformixInfo.INFORMIX_USER):
        """
        清理失效日志
        """
        log_path = os.path.join(self.local_logbackup_path, 'data', '1', self._instance_name_curr,
                                self._instance_name_curr, self.servernum)

        clen_arch_log_cmd = f'su - {db_user} -c "rm -rf {log_path}"'
        LOGGER.info(f"Start to delete invalid logs, cmd: {clen_arch_log_cmd}.")
        ret, out, err = execute_cmd(clen_arch_log_cmd)
        if ret != CMDResult.SUCCESS:
            LOGGER.info(f"ret: {ret}, out: {out}, err: {err}")

    def paras_result_set_errinfo(self, err):
        """
        解析恢复命令返回结果，并设置待上报的错误信息
        """
        pass
    
    def get_ixbar_path(self, servernum):
        log_dir = os.path.dirname(self._log_area_list[0])
        meta_file = os.path.join(log_dir, f"{self.full_copy_id}.meta")
        if not os.path.exists(meta_file):
            LOGGER.info(f"Meta file path not exist: {meta_file}.")
            return ""
        LOGGER.info(f"Meta file path: {meta_file}.")
        log_copy_dic = {}
        with open(meta_file, "r") as file:
            text = file.readlines()
            for line in text:
                log_copy = line.split(";")
                log_copy_dic[log_copy[1].split("~")[1]] = log_copy[0]
        log_copy_time_list = list(map(lambda x: int(x), log_copy_dic.keys()))
        restore_time = int(self.restore_type)
        for log_copy_time in log_copy_time_list:
            if restore_time <= log_copy_time:
                log_copy_id = log_copy_dic.get(str(log_copy_time))
                break
        ixbar_path = os.path.join(log_dir, log_copy_id, log_copy_id, f'ixbar.{servernum}')
        return ixbar_path
        

    def pre_ixbar_file(self):
        """
        拷贝备份副本中的ixbar文件到$INFORMIXDIR/etc/目录下
        :return:
        """
        servernum = self.arch_copy_server_num if self.arch_copy_server_num else self.copy_server_num
        copy_id = self.arch_copy_id if self.arch_copy_id else self.last_copy_id
        if not self.restore_type:
            src_ixbar_path = os.path.join(self._data_area, copy_id, f'ixbar.{servernum}')
        else:
            src_ixbar_path = self.get_ixbar_path(servernum)
        if not src_ixbar_path:
            return False
        LOGGER.info(f"Get ixbar path: {src_ixbar_path}")
        informix_dir = get_informix_dir(self.db_user)
        if not informix_dir:
            LOGGER.error("Get informix dir fail", self.get_log_common())
            return False
        ixbar_mid_path = "etc" if self.db_user == InformixInfo.INFORMIX_USER else "backups"
        dst_ixbar_path = os.path.join(informix_dir, ixbar_mid_path, f'ixbar.{self.servernum}')
        cmd = f'chmod -R 755 {os.path.dirname(src_ixbar_path)}'
        ret, out, err = execute_cmd(cmd)
        if ret != CMDResult.SUCCESS:
            LOGGER.error(f"{cmd} error.")
            return False
        ret = exec_cp_cmd(src_ixbar_path, dst_ixbar_path, self.db_user, '-f', is_check_path_owner=False)
        if not ret:
            LOGGER.error("Copy ixbar fail. %s", self.get_log_common())
            return False
        if not set_permisson(dst_ixbar_path, PERMISSION_640):
            LOGGER.error(f"Failed to set the permission on the ixbar file: {dst_ixbar_path}")
            return False
        instance_name_curr = self.arch_copy_instance_name if self.arch_copy_instance_name else self.copy_instance_name
        oncfg_file = f"oncfg_{instance_name_curr}.{servernum}"
        if not self.restore_type:
            src_oncfg_path = os.path.join(self._data_area, copy_id, oncfg_file)
        else:
            item_id = self._log_area_list[-1].split('/')[-1]
            src_oncfg_path = os.path.join(self._log_area_list[-1], item_id, oncfg_file)
        LOGGER.info(f"Get oncfg path: {src_oncfg_path}")
        oncfg_file_path = os.path.join(informix_dir, "etc", f"oncfg_{self._instance_name_curr}.{self.servernum}")
        ret = exec_cp_cmd(src_oncfg_path, oncfg_file_path, self.db_user, '-f', is_check_path_owner=False)
        if not ret:
            LOGGER.error("Copy oncfg fail. %s", self.get_log_common())
            return False
        return True

    def return_err_info(self):
        log_detail = LogDetail(log_info_param=[self._job_id],
                               log_info=ReportDBLabel.RESTORE_SUB_FAILED,
                               log_level=DBLogLevel.ERROR,
                               log_detail=self.log_detail_errcode,
                               log_detail_param=self.log_detail_param
                               )
        output = SubJobDetails(task_id=self._job_id, sub_task_id=self._sub_job_id,
                               task_status=SubJobStatusEnum.FAILED.value,
                               progress=100,
                               log_detail=[log_detail])
        return output.dict(by_alias=True)

    def pre_copy_log(self):
        LOGGER.info("Enter pre_copy_log")
        servernum = self.arch_copy_server_num if self.arch_copy_server_num else self.copy_server_num
        _instance_name_curr = self.arch_copy_instance_name if self.arch_copy_instance_name else self.copy_instance_name
        LOGGER.info(f"pre_copy_log {self.restore_type}")
        if self.restore_type:
            for item in self._log_area_list:
                item_list = item.split("/")
                src_path = os.path.join(item, item_list[-1])
                dest_path = os.path.join(self._data_area, self.last_copy_id, 'data/1/', _instance_name_curr,
                                         _instance_name_curr, servernum)
                if not backup_mount_bind(src_path, dest_path):
                    return False
        else:
            if not self.pre_log_copy(servernum, _instance_name_curr):
                return False

        return self.pre_data_copy_mount_bind()

    def pre_data_copy_mount_bind(self):
        for copyid in self.base_backupset:
            src_path = os.path.join(self._data_area, copyid, 'data', copyid)
            dest_path = os.path.join(self._data_area, self.last_copy_id, 'data', copyid)
            if not mkdir_set_permissions(dest_path, 'rdadmin'):
                return False
            if not backup_mount_bind(src_path, dest_path):
                return False
        return True

    def post_copy_umount_bind(self):
        LOGGER.info(f"Enter post_copy_umount_bind")
        _instance_name_curr = self.arch_copy_instance_name if self.arch_copy_instance_name else self.copy_instance_name
        log_db_path = os.path.join(f"{get_install_head_path()}/DataBackup", "meta", "1/objectmeta",
                                   f"{_instance_name_curr}", f'{_instance_name_curr}.db')
        remove_file_dir(log_db_path)
        return True

    def pre_log_copy(self, servernum, _instance_name_curr):
        for copyid in self.base_backupset:
            dest_path = os.path.join(self._data_area, self.last_copy_id, 'data/1/', _instance_name_curr,
                                     _instance_name_curr, servernum)
            src_path = os.path.join(self._data_area, copyid, 'data/1/', _instance_name_curr, _instance_name_curr,
                                    servernum)
            if not os.path.exists(src_path):
                return False
            file_list = os.listdir(src_path)
            for file in file_list:
                ret = exec_cp_cmd(os.path.join(src_path, file), dest_path, None, None, is_check_path_owner=False)
                if not ret:
                    return False
        return True

    def update_log_db_file(self):
        LOGGER.info("Start to modify the database log backup record.")
        if not self.restore_type:
            return True
        _instance_name_curr = self.arch_copy_instance_name if self.arch_copy_instance_name else self.copy_instance_name
        dst_db_path = os.path.join(self._meta_area, "meta", self.last_copy_id, 'objectmeta', _instance_name_curr,
                                   f"{_instance_name_curr}.db")
        dest_path = os.path.join(self._data_area, self.last_copy_id)
        for item in self._log_area_list:
            item_list = item.split("/")
            src_db_path = os.path.join(item, item_list[-1], f"{_instance_name_curr}.db")
            src_path = os.path.join(item, item_list[-1])
            LOGGER.info(f"From {src_db_path}")
            LOGGER.info(f"To {dst_db_path}")
            LOGGER.info(f"Src: {src_path}, Dest:{dest_path}")
            copy_data_model = CopyDataModel(src_db_path, dst_db_path, self._job_id, src_path, dest_path, self.db_user)
            if not change_dir_permission(copy_data_model):
                return False
            if not aggregate_single_copy_object_data(copy_data_model):
                return False
        return True

    def update_data_db_file(self):
        LOGGER.info("Start to modify the database data backup record.")
        _instance_name_curr = self.arch_copy_instance_name if self.arch_copy_instance_name else self.copy_instance_name
        dst_db_path = os.path.join(self._meta_area, "meta", self.last_copy_id, 'objectmeta', _instance_name_curr,
                                   f"{_instance_name_curr}.db")
        LOGGER.info(f"To {dst_db_path}")
        dest_path = os.path.join(self._data_area, self.last_copy_id)
        for copy_id in self.base_backupset:
            src_db_path = os.path.join(self._meta_area, "meta", copy_id, 'objectmeta', _instance_name_curr,
                                       f"{_instance_name_curr}.db")
            LOGGER.info(f"From {src_db_path}")
            src_path = os.path.join(self._data_area, copy_id)
            LOGGER.info(f"Src: {src_path}, Dest:{dest_path}")
            copy_data_model = CopyDataModel(src_db_path, dst_db_path, self._job_id, src_path, dest_path, self.db_user)
            if not change_dir_permission(copy_data_model):
                return False
            if not aggregate_single_copy_object_data(copy_data_model):
                return False
        return True

    def update_db_file_and_copy_file_by_cur_instance(self):
        _instance_name_curr = self.arch_copy_instance_name if self.arch_copy_instance_name else self.copy_instance_name
        servernum = self.arch_copy_server_num if self.arch_copy_server_num else self.copy_server_num
        dst_db_path = os.path.join(self._meta_area, "meta", self.last_copy_id, 'objectmeta', _instance_name_curr,
                                   f"{_instance_name_curr}.db")
        if not update_db_new_instance_name_and_id(dst_db_path, self._instance_name_curr, self.servernum,
                                                  _instance_name_curr, servernum):
            LOGGER.error(f"Update DB filed.")
            return False
        return True

    def clean_instance_process(self):
        LOGGER.info("Start to clear the instance process.")
        ret, out, err = exec_cmd_with_informix_env(self._env_dict, InformixBaseCommand.CLEAN_INSTANCE, self.db_user)
        if ret != CMDResult.SUCCESS:
            LOGGER.warning("Start instance fail", self.get_log_common())
            return False
        return True

    def _support_progress(self, task_status, progress=1, log_detail=None, log_level=DBLogLevel.INFO, data_size=0):
        log_detail_inst = None
        if log_detail:
            log_detail_inst = [
                log_detail
            ]
        progress_info = SubJobDetails(task_id=self._job_id, task_status=task_status, progress=progress,
                                      sub_task_id=self._sub_job_id, log_detail=log_detail_inst, data_size=data_size)
        self.return_result = progress_info
        self.update_result()

    def _restart_instance(self):
        """
        由于Gbase8s在某些情况下恢复后数据库状态为不可访问，需要在恢复后执行数据库重启。
        首先停止数据库，然后启动数据库，在执行失败情况下不影响后续功能故不会抛出异常，待任务结束后手动尝试重启数据库即可。
        Informix 环境无需该操作，如果检测到不是GBase8s环境直接返回成功
        """
        if self.db_user != GbaseInfo.GBASE_USER:
            return True
        cmd = cmd_format(GbaseCommand.EXEC_WITH_GBASEDBT, GbaseCommand.STOP_COMMAND)
        ret, out, err = execute_cmd(cmd)
        if ret != CMDResult.SUCCESS:
            self.paras_result_set_errinfo(err)
            LOGGER.error("Stop GBase 8S instance failed! Please check.", self.get_log_common())
            return False
        LOGGER.info("Successfully stopped GBase 8S instance")
        cmd = cmd_format(GbaseCommand.EXEC_WITH_GBASEDBT, GbaseCommand.START_COMMAND)
        ret, out, err = execute_cmd(cmd)
        if ret != CMDResult.SUCCESS:
            self.paras_result_set_errinfo(err)
            LOGGER.error("Start GBase 8S instance failed! Please check.", self.get_log_common())
            return False
        LOGGER.info("Successfully started GBase 8S instance")
        return True
